<!DOCTYPE html>
<html>
<head>
<title>DAY02</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
/* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */
/* Author: Nicolas Hery - http://nicolashery.com */
/* Version: b13fe65ca28d2e568c6ed5d7f06581183df8f2ff */
/* Source: https://github.com/nicolahery/markdownpad-github */

/* RESET
=============================================================================*/

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
}

/* BODY
=============================================================================*/

body {
  font-family: Helvetica, arial, freesans, clean, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: #333;
  background-color: #fff;
  padding: 20px;
  max-width: 960px;
  margin: 0 auto;
}

body>*:first-child {
  margin-top: 0 !important;
}

body>*:last-child {
  margin-bottom: 0 !important;
}

/* BLOCKS
=============================================================================*/

p, blockquote, ul, ol, dl, table, pre {
  margin: 15px 0;
}

/* HEADERS
=============================================================================*/

h1, h2, h3, h4, h5, h6 {
  margin: 20px 0 10px;
  padding: 0;
  font-weight: bold;
  -webkit-font-smoothing: antialiased;
}

h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
  font-size: inherit;
}

h1 {
  font-size: 28px;
  color: #000;
}

h2 {
  font-size: 24px;
  border-bottom: 1px solid #ccc;
  color: #000;
}

h3 {
  font-size: 18px;
}

h4 {
  font-size: 16px;
}

h5 {
  font-size: 14px;
}

h6 {
  color: #777;
  font-size: 14px;
}

body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
  margin-top: 0;
  padding-top: 0;
}

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
  margin-top: 0;
  padding-top: 0;
}

h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
  margin-top: 10px;
}

/* LINKS
=============================================================================*/

a {
  color: #4183C4;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* LISTS
=============================================================================*/

ul, ol {
  padding-left: 30px;
}

ul li > :first-child, 
ol li > :first-child, 
ul li ul:first-of-type, 
ol li ol:first-of-type, 
ul li ol:first-of-type, 
ol li ul:first-of-type {
  margin-top: 0px;
}

ul ul, ul ol, ol ol, ol ul {
  margin-bottom: 0;
}

dl {
  padding: 0;
}

dl dt {
  font-size: 14px;
  font-weight: bold;
  font-style: italic;
  padding: 0;
  margin: 15px 0 5px;
}

dl dt:first-child {
  padding: 0;
}

dl dt>:first-child {
  margin-top: 0px;
}

dl dt>:last-child {
  margin-bottom: 0px;
}

dl dd {
  margin: 0 0 15px;
  padding: 0 15px;
}

dl dd>:first-child {
  margin-top: 0px;
}

dl dd>:last-child {
  margin-bottom: 0px;
}

/* CODE
=============================================================================*/

pre, code, tt {
  font-size: 12px;
  font-family: Consolas, "Liberation Mono", Courier, monospace;
}

code, tt {
  margin: 0 0px;
  padding: 0px 0px;
  white-space: nowrap;
  border: 1px solid #eaeaea;
  background-color: #f8f8f8;
  border-radius: 3px;
}

pre>code {
  margin: 0;
  padding: 0;
  white-space: pre;
  border: none;
  background: transparent;
}

pre {
  background-color: #f8f8f8;
  border: 1px solid #ccc;
  font-size: 13px;
  line-height: 19px;
  overflow: auto;
  padding: 6px 10px;
  border-radius: 3px;
}

pre code, pre tt {
  background-color: transparent;
  border: none;
}

kbd {
    -moz-border-bottom-colors: none;
    -moz-border-left-colors: none;
    -moz-border-right-colors: none;
    -moz-border-top-colors: none;
    background-color: #DDDDDD;
    background-image: linear-gradient(#F1F1F1, #DDDDDD);
    background-repeat: repeat-x;
    border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
    border-image: none;
    border-radius: 2px 2px 2px 2px;
    border-style: solid;
    border-width: 1px;
    font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
    line-height: 10px;
    padding: 1px 4px;
}

/* QUOTES
=============================================================================*/

blockquote {
  border-left: 4px solid #DDD;
  padding: 0 15px;
  color: #777;
}

blockquote>:first-child {
  margin-top: 0px;
}

blockquote>:last-child {
  margin-bottom: 0px;
}

/* HORIZONTAL RULES
=============================================================================*/

hr {
  clear: both;
  margin: 15px 0;
  height: 0px;
  overflow: hidden;
  border: none;
  background: transparent;
  border-bottom: 4px solid #ddd;
  padding: 0;
}

/* TABLES
=============================================================================*/

table th {
  font-weight: bold;
}

table th, table td {
  border: 1px solid #ccc;
  padding: 6px 13px;
}

table tr {
  border-top: 1px solid #ccc;
  background-color: #fff;
}

table tr:nth-child(2n) {
  background-color: #f8f8f8;
}

/* IMAGES
=============================================================================*/

img {
  max-width: 100%
}
</style>
</head>
<body>
<h3>5. 用户-注册-业务层</h3>
<p><strong>5.1. 业务层的基本定位</strong></p>
<p>在MVC设计理念中，M表示的是Model，即数据模型，它由持久层和业务层共同构成！持久层负责完成数据操作，即增删改查，业务层负责组织业务流程和管理业务逻辑，业务更多在表现为用户能操作的某1个“功能”，但是，对于程序员来说，可能是更多的细小的功能来组成的！之所以需要业务层，是因为需要通过业务层来保证数据的安全(数据必须经过业务层的各种流程和逻辑才产生或发生变化)！</p>
<p><strong>5.2. 规划异常</strong></p>
<p>考虑当前的“注册”功能可能会抛出哪些异常！</p>
<p>在“注册”时，需要先检查用户名是否被占用，如果已经被占用，则不允许注册 ，就是一种“操作失败”，则应该有“用户名占用异常”被抛出！</p>
<p>如果用户名没有被占用，则允许注册，将执行数据表的<code>INSERT</code>操作，凡是数据表的增、删、改操作，都是有可能出现操作失败的！则应该抛出“插入数据异常”。</p>
<p>在业务层，一旦视为“操作失败”，对异常的处理都应该是“抛出”，而不是自行处理！因为业务层不适合进行处理！</p>
<p>所以，此次需要创建2个异常类<code>cn.tedu.store.service.ex.UsernameDuplicateException</code>和<code>cn.tedu.store.service.ex.InsertException</code>，另外还需要创建这2个异常类公共的父级异常类<code>cn.tedu.store.service.ex.ServiceException</code>，它们都应该是<code>RuntimeException</code>的子孙类：</p>
<pre><code>RuntimeException
    ServiceException
        UsernameDuplicateException
        InsertException
</code></pre>

<p><strong>5.3. 接口与抽象方法</strong></p>
<p>在设计业务层时，应该先有业务层的接口，并在接口中定义抽象方法，后续，外界(当前Model以外，例如Controller或其它Service等)调用时，是基于接口来声明对象，并调用方法的，所以，此处使用接口是一种解耦的做法！</p>
<p>先创建<code>cn.tedu.store.service.IUserService</code>业务层接口，并添加抽象方法：</p>
<pre><code>void reg(User user) throws UsernameDuplicateException, InsertException;
</code></pre>

<p>关于业务层的抽象方法的设计原则：</p>
<ol>
<li>
<p>返回值：仅以操作成功为前提来设计返回值；</p>
</li>
<li>
<p>方法名：尽量体现业务，例如使用<code>reg</code>或<code>regist</code>或<code>register</code>表示注册，使用<code>login</code>表示登录；</p>
</li>
<li>
<p>参数列表：一定是客户端可以提供的数据，或来自于Session中的数据，且足够调用持久层的各方法；</p>
</li>
<li>
<p>异常：把用户操作失败的可能，都设计出各种异常，并把这些异常都添加到方法的声明中。</p>
</li>
</ol>
<p><strong>5.4. 实现类与重写方法</strong></p>
<p>创建<code>cn.tedu.store.service.impl.UserServiceImpl</code>业务层实现类，实现以上<code>IUserService</code>接口，添加<code>@Service</code>注解，并在类中声明<code>@Autowired private UserMapper userMapper;</code>持久层对象：</p>
<pre><code>@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

}
</code></pre>

<p>然后，重写接口中定义的抽象方法：</p>
<pre><code>public void reg(User user) throws UsernameDuplicateException, InsertException {
    // 根据参数user对象中的username属性查询数据：userMapper.findByUsername()
    // 判断查询结果是否不为null（查询结果是存在的）
    // 是：用户名已被占用，抛出UsernameDuplicateException

    // TODO 得到盐值
    // TODO 基于参数user对象中的password进行加密，得到加密后的密码
    // TODO 将加密后的密码和盐值封装到user中

    // 将user中的isDelete设置为0

    // 封装user中的4个日志属性

    // 执行注册：userMapper.insert(user)
}
</code></pre>

<p>具体实现为：</p>
<pre><code>@Override
public void reg(User user) throws UsernameDuplicateException, InsertException {
    // 根据参数user对象中的username属性查询数据：userMapper.findByUsername()
    String username = user.getUsername();
    User result = userMapper.findByUsername(username);
    // 判断查询结果是否不为null（查询结果是存在的）
    if (result != null) {
        // 是：用户名已被占用，抛出UsernameDuplicateException
        throw new UsernameDuplicateException(
            &quot;注册失败！尝试注册的用户名(&quot; + username + &quot;)已经被占用！&quot;);
    }

    // 得到盐值
    System.err.println(&quot;reg() &gt; password=&quot; + user.getPassword());
    String salt = UUID.randomUUID().toString().toUpperCase();
    // 基于参数user对象中的password进行加密，得到加密后的密码
    String md5Password = getMd5Password(user.getPassword(), salt);
    // 将加密后的密码和盐值封装到user中
    user.setSalt(salt);
    user.setPassword(md5Password);
    System.err.println(&quot;reg() &gt; salt=&quot; + salt);
    System.err.println(&quot;reg() &gt; md5Password=&quot; + md5Password);

    // 将user中的isDelete设置为0
    user.setIsDelete(0);

    // 封装user中的4个日志属性
    Date now = new Date();
    user.setCreatedUser(username);
    user.setCreatedTime(now);
    user.setModifiedUser(username);
    user.setModifiedTime(now);

    // 执行注册：userMapper.insert(user)
    Integer rows = userMapper.insert(user);
    if (rows != 1) {
        throw new InsertException(
            &quot;注册失败！写入数据时出现未知错误！请联系系统管理员！&quot;);
    }
}

/**
 * 对密码进行加密
 * @param password 原始密码
 * @param salt 盐值
 * @return 加密后的密码
 */
private String getMd5Password(String password, String salt) {
    // 规则：对password+salt进行3重加密
    String str = password + salt;
    for (int i = 0; i &lt; 3; i++) {
        str = DigestUtils
            .md5DigestAsHex(str.getBytes()).toUpperCase();
    }
    return str;
}
</code></pre>

<p><strong>注意：特别是在执行插入数据时，业务层除了需要保证业务流程和业务逻辑，还需要保证数据的完整性！</strong>*</p>
<p>完成后，在<strong>src/test/java</strong>下创建<code>cn.tedu.store.service.UserServiceTests</code>测试类，编写并执行单元测试：</p>
<pre><code>@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTests {

    @Autowired
    IUserService service;

    @Test
    public void reg() {
        try {
            User user = new User();
            user.setUsername(&quot;Digest&quot;);
            user.setPassword(&quot;1234&quot;);
            service.reg(user);
            System.err.println(&quot;OK.&quot;);
        } catch (ServiceException e) {
            System.err.println(e.getClass().getName());
            System.err.println(e.getMessage());
        }
    }

}
</code></pre>

<h3>6. 用户-注册-控制器层</h3>
<p><strong>6.1. 处理异常</strong></p>
<p>在编写处理请求的控制器及方法之前，还应该对异常进行统一处理！</p>
<p>首先，需要创建响应结果的数据类型<code>cn.tedu.store.util.JsonResult</code>：</p>
<pre><code>/**
 * 向客户端响应操作结果的数据类型
 * 
 * @param &lt;T&gt; 向客户端响应的数据的类型
 */
public class JsonResult&lt;T&gt; {

    private Integer state;
    private String message;
    private T data;

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}
</code></pre>

<p>在统一处理异常时，相关代码只能作用于当前控制器类，为了使得整个项目都可以使用这个统一处理异常的机制，应该把相应代码添加在控制器类的基类<code>cn.tedu.store.controller.BaseController</code>中，所以：</p>
<pre><code>public class BaseController {

    @ExceptionHandler(ServiceException.class)
    public JsonResult&lt;Void&gt; handleException(Throwable e) {
        JsonResult&lt;Void&gt; jr = new JsonResult&lt;Void&gt;();
        jr.setMessage(e.getMessage());

        if (e instanceof UsernameDuplicateException) {
            jr.setState(2);
        } else if (e instanceof InsertException) {
            jr.setState(3);
        }

        return js;
    }

}
</code></pre>

<p><strong>6.2. 设计请求</strong></p>
<p>需要事先规划用户的请求是什么样的，例如用户向哪个URL发出请求表示“注册”，请求方式是哪种，是否需要提交某些请求参数等，以及最终服务器端向客户端响应什么样的数据：</p>
<pre><code>请求路径：/users/reg
请求参数：User user
请求类型：POST
响应数据：JsonResult&lt;Void&gt;
</code></pre>

<p><strong>6.3. 处理请求</strong></p>
<p>创建<code>cn.tedu.store.controller.UserController</code>控制器类，继承自<code>BaseController</code>，添加<code>@RestController</code>和<code>@RequestMapping(&quot;users&quot;)</code>注解，并在类中添加<code>@Autowired private IUserService userService;</code>业务层对象，并在类中添加处理请求的方法：</p>
<pre><code>@ReuqestMapping(&quot;reg&quot;)
public JsonResult&lt;Void&gt; reg(User user) {
    // 执行注册
    userService.reg(user);
    // 返回成功
    JsonResult&lt;Void&gt; jr = new JsonResult&lt;Void&gt;();
    jr.setState(1);
    return jr;
}
</code></pre>

<p>打开浏览器，输入<code>http://localhost:8080/users/reg?username=Alex&amp;password=1234</code>进行测试。</p>
<h3>7. 用户-注册-前端页面</h3>
<h3>8. 用户-登录-持久层</h3>
<h3>9. 用户-登录-业务层</h3>
<h3>10. 用户-登录-控制器层</h3>
<h3>11. 用户-登录-前端页面</h3>
<h3>--------------------------------------------------</h3>
<h3>附1：密码加密</h3>
<p>加密算法并不适合对密码进行加密！因为，所以的加密算法都是可以逆运算的，如果使用的算法和加密时使用的参数是已知的，则可以轻松推算出原始数据！而密码安全问题主要源自内部泄密！也就是说，数据库的数据能泄密，可以认为算法和加密参数也是有泄密的可能的！</p>
<p>对于密码安全问题而言，最好的做法就是将密码进行加密，却任何人都无法解密！则，在密码加密这个问题上，所有的加密算法都是不适用的！只能使用摘要算法！</p>
<p>摘要算法的全称是“消息摘要算法”，这种算法的特征有：</p>
<ol>
<li>
<p>消息原文相同时，得到的摘要是相同的；</p>
</li>
<li>
<p>使用的摘要算法没有发生变化时，无论原文长度是多少，摘要的长度是固定的；</p>
</li>
<li>
<p>消息原文不同时，得到的摘要几乎不会相同！</p>
</li>
</ol>
<p>在消息摘要领域中，一定存在N种不同的原文，可以计算得到完全相同的摘要！</p>
<p>使用MD5算法执行摘要运算时，得到的结果都是32位长度的十六进制数，转换成二进制就需要128位，所以，MD5也被称之为128位的算法！</p>
<p>如果有2个不同的原文，可以得到相同的摘要，这种状况称之为“碰撞”，在有限长度的原文中，得到相同的摘要的概率是极低，甚至就是不可能的！</p>
<p>所以，真正用于对密码加密的算法，都是摘要算法，主要原因是因为它不可逆，且在有限长度的原文的基础之上，几乎不可能发生碰撞！</p>
<p>常见的摘要算法有MD5家族和SHA系列，例如：MD2、MD4、MD5、SHA1、SHA128、SHA256、SHA384、SHA512……其中，MD家族的都是128的，SHA系列名称后有位数的，就是对应的位数。</p>
<p>关于MD5或相关摘要算法的破解研究是存在的，主要是针对算法的碰撞攻击，而并不是尝试执行逆运算来得到原始数据！</p>
<p>另外，还有许多网站号称可以在线破解MD5，只需要将摘要结果填进去，就可以查到原始数据，例如填入<code>e10adc3949ba59abbe56e057f20f883e</code>就可以查出<code>123456</code>，事实上，这些网站是记录了大量的原始数据与MD5摘要数据的对应关系，如果原始数据比较简单，或者是常用密码值，被这些网站收录的可能性就非常大！所以，为了提升密码的安全性，保证密码不被这些网站“破解”，可以采取的做法：</p>
<ol>
<li>
<p>增强原始密码的安全强度，例如从组成元素、长度方面提出更高要求；</p>
</li>
<li>
<p>反复执行加密，即多重加密；</p>
</li>
<li>
<p>加盐；</p>
</li>
<li>
<p>综合以上所有方式。</p>
<p>select 原始数据 from 表 where 摘要数据=?</p>
<p>10 + 52 + ? &gt; 80</p>
<p>1 &gt; 80
2 &gt; 6400
3 &gt; 480000
4 &gt; 40000000
5 &gt; 3200000000</p>
<p>8 &gt; </p>
<p>123456
e10adc3949ba59abbe56e057f20f883e</p>
<p>1
c4ca4238a0b923820dcc509a6f75849b</p>
<p>111111111111111111111111111111111111111111
636fa4bad5bf92137374e947b3c424f0</p>
</li>
</ol>

</body>
</html>
<!-- This document was created with MarkdownPad, the Markdown editor for Windows (http://markdownpad.com) -->
